/*
Authors: Eli Yucht 300388949 and Lior Fass 300069705
Project: SCP (Switch Schedule Policy)
Description: Scheduler class implementation
*/

# define _CRT_SECURE_NO_WARNINGS // to avoid unsafe warnings
#include "scheduler.h"

/* Scheduler class constructor:
	Initialize new Scheduler's members */
Scheduler::Scheduler()
{
	infile_p = NULL;
	outfile_p = NULL;
	current_time = 0;
	head_flow_queue = NULL;
	tail_flow_queue = NULL;
	current_flow = NULL;
	need_to_load_next_time = false;
	end_of_input = false;
	empty_queues = false;
	num_of_flows = 0;
	counter_of_empty_flows = 0;

}

/* Scheduler class destructor:
	Close the input and the output files
	Delete (and so free the memory of) all the flows in the queue */
Scheduler::~Scheduler()
{
	fclose(infile_p);
	fclose(outfile_p);
	Flow* temp_flow = head_flow_queue;
	while (temp_flow != NULL)
	{
		head_flow_queue = head_flow_queue->next_flow;
		delete temp_flow;
		temp_flow = head_flow_queue;
	}

}
/* This function initialize the scheduler:
	1. set the quantum
	2. open the input and output files
	3. read the firs line from the input file and set the currnet time
	4. load to the scheduler all the packets that arrived until current time
	
	On success return 0, otherwise return 1*/
int Scheduler::Initialize_Scheduler(int inQuantum, char* infile_name, char* outfile_name)
{
	quantum = inQuantum;

	infile_p = fopen(infile_name, "r");
	if (infile_p == NULL)
	{
		cerr << "Failed to open input file.\n";
		return 1;
	}
	outfile_p = fopen(outfile_name, "w");
	if (outfile_p == NULL)
	{
		cerr << "Failed to open output file.\n";
		return 1;
	}
	if (Get_Pkt_From_File(true))
	{
		cerr << "Failed to Initialize Scheduler.\n";
		return 1;
	}
	if (Load_data_to_Schedule())
	{
		cerr << "Failed to Load data from file.\n";
		return 1;

	}

	return 0;
}

/* This function loads new packets (all the packets that arrived until current time)
	from the input file to the scheduler's data structure.

	return 0 if success, or -1 if some error occurred
*/
int Scheduler::Load_data_to_Schedule()
{
	if (nextPacket.pkt.arrive_time <= current_time)
	{
		if (need_to_load_next_time)
		{
			if (Load_Flow())
			{
				cerr << "Error while trying to insert new packet.\n";
				return -1;
			}
			need_to_load_next_time = false;
		}


	}
	else if (empty_queues) // need to promote time
	{
		current_time = nextPacket.pkt.arrive_time;
		if (Load_Flow())
		{
			cerr << "Error while trying to insert new packet.\n";
			return -1;
		}
	}

	else
		return 0;

	int return_val = Get_Pkt_From_File(false);
	while (!return_val)
		return_val = Get_Pkt_From_File(false);
	if (return_val == -1)
	{
		cerr << "Error while trying to insert new packet.\n";
		return -1;
	}
	return 0;

}

/* This function read a new packet from the input file 
	and return (int) value as follow:

	-1 if some error occurred
	2  if we reach to the end of file
	3	if the arriving time of the next packet is greater than the current time
		and so we don't load it to the scheduler yet
	0 if we load (successfully) the next packet to the scheduler's data structure
*/
int Scheduler::Get_Pkt_From_File(bool first_time)
{

	if (fgets(line_buf, MAX_LINE_BUF, infile_p) == NULL && !feof(infile_p))
	{
		cerr << "Failed to read from file.\n";
		return -1;
	}
	if (feof(infile_p)) // in case of end of file
	{
		end_of_input = true;
		return 2;
	}
	char* token = strtok(line_buf, " \n");

	nextPacket.pkt.pktID = atol(token);

	for (int i = 0; i < 7; i++)
	{
		token = strtok(NULL, " \n");
		if (token == NULL && i != 6)
		{
			cerr << "Strtok() failed.\n";
			return -1;

		}
		else
		{
			switch (i)
			{
			case 0:
				nextPacket.pkt.arrive_time = atol(token);
				break;
			case 1:
				nextPacket.sadd = token;
				break;
			case 2:
				nextPacket.sport = (unsigned short int)atoi(token);
				break;
			case 3:
				nextPacket.dadd = token;
				break;
			case 4:
				nextPacket.dport = (unsigned short int)atoi(token);
				break;
			case 5:
				nextPacket.pkt.length = (unsigned short int)atoi(token);
				break;
			case 6:
				if (token != NULL)
					nextPacket.weight = (unsigned short int)atoi(token);
				else
					nextPacket.weight = 1;
			}
		}
	}
	if (first_time)
	{
		current_time = nextPacket.pkt.arrive_time;
		try{
			head_flow_queue = new Flow(nextPacket.sadd, nextPacket.sport, nextPacket.dadd, nextPacket.dport, nextPacket.weight);
		}
		catch (std::bad_alloc& ba)
		{
			cerr << "bad_alloc caught: " << ba.what() << '\n';
			return -1;
		}
		tail_flow_queue = head_flow_queue;
		num_of_flows++;
		if (head_flow_queue->Add_Pkt_To_Flow(nextPacket.pkt.pktID, nextPacket.pkt.arrive_time, nextPacket.pkt.length))
		{
			cerr << "Failed to add packet to queue\n";
			return -1;
		}
	}
	else if (current_time < nextPacket.pkt.arrive_time)
	{
		need_to_load_next_time = true;
		return 3;
	}
	else // not the first time
	{
		if (Load_Flow())
		{
			cerr << "Error while trying to insert new packet.\n";
			return -1;
		}
	}
	need_to_load_next_time = false;
	return 0;
}


/* This function search in the flows queue 
	if the new packet's flow that was read from the input file
	already exist or dosen't.
	
	if the flow already exist a pointer to that flow is return
	else NULL is return
*/
Flow* Scheduler::Find_Flow()
{
	Flow* temp_flow = head_flow_queue;

	while (temp_flow != NULL)
	{
		if (temp_flow->Check_Flow(nextPacket.sadd, nextPacket.sport, nextPacket.dadd, nextPacket.dport))
			return temp_flow;
		else
			temp_flow = temp_flow->next_flow;
	}
	return temp_flow;
}

/* This function load the new (last) packet that was read from
	the input file to the Scheduler's data structure.

	if some error occurred it returns -1
	otherwise (on success) returns 0
*/

int Scheduler::Load_Flow()
{
	Flow* temp_flow = Find_Flow();
	if (temp_flow != NULL)
	{
		if (temp_flow->Add_Pkt_To_Flow(nextPacket.pkt.pktID, nextPacket.pkt.arrive_time, nextPacket.pkt.length))
		{
			cerr << "Failed to add packet to queue.\n";
			return -1;
		}
	}
	else // need to create new flow
	{
		try{
			temp_flow = new Flow(nextPacket.sadd, nextPacket.sport, nextPacket.dadd, nextPacket.dport, nextPacket.weight);
		}
		catch (std::bad_alloc& ba)
		{
			cerr << "bad_alloc caught: " << ba.what() << '\n';
			return -1;
		}
		tail_flow_queue->next_flow = temp_flow;
		tail_flow_queue = temp_flow;
		num_of_flows++;
		if (temp_flow->Add_Pkt_To_Flow(nextPacket.pkt.pktID, nextPacket.pkt.arrive_time, nextPacket.pkt.length))
		{
			cerr << "Failed to add packet to queue.\n";
			return -1;
		}
	}
	return 0;

}

/* This is the main Scheduling function, it holds the common structure of Scheduling
	and let all Derived Schedulers implement their different decision using inheritance
	of 3 functions:
	1. ScheduleCurrentFlow() - a must implement function for scheduling current flow.
	2. HandleCurrentFlowEmpty() - optional function for special treatment of empty flow.
	3. HandleAllEmpty() - optional function for special treatment of All Flows Empty

	Steps:
	1. start the scheduling process with the first flow
	2. while there are still packets(in the input file or the flows queues)
	2.1 if all queues are empty - run HandleAllEmpty
	2.2 else if current flow empty - run HandleCurrentFlowEmpty
	2.3 if current flow had packets run ScheduleCurrentFlow (runs According to the derived scheduler class)
		advance to next flow


	if some error occurred it returns 1
	otherwise (on success) returns 0
*/
int Scheduler::RunScheduler()
{
	current_flow = head_flow_queue;
	while (!end_of_input || !empty_queues)
	{
		if (empty_queues) // need to load packets from file and promote time
		{
			if (HandleAllEmpty())
			{
				cerr << "Failed to load data from file.\n";
				return 1;
			}
		}
		else if (current_flow->Get1PacketLength() == 0) // the current queue is empty
		{
			HandleCurrentFlowEmpty();
		}
		else // there is at least one packet in current flow
		{
			if (ScheduleCurrentFlow()) // Schedule policy according to Scheduler's type
			{
				cerr << "Failed to do schedule.\n";
				return 1;
			}
			counter_of_empty_flows = 0;
			Go_to_Next_Flow();
		}
		if (counter_of_empty_flows == num_of_flows)
			empty_queues = true;
	}
	return 0;
}

/*
*	this virtual function handles ALL Flows Are Empty situation
*	which means Load data to Schedule, and jump to next packet time, 
*	in addition release empty_queues flag zerorize counter and change current_flow to head_flow
*	schedulers that need special treatment should ovveride this function
*/
int Scheduler::HandleAllEmpty()
{
	if (Load_data_to_Schedule())
		return 1;
	empty_queues = false;
	counter_of_empty_flows = 0;
	current_flow = head_flow_queue;
	return 0;
}

/*
*	this virtual function handles Current Flow Empty situation
*	which means advance counter_of_empty_flows by one, and Go_to_next_flow
*	schedulers that need special treatment should ovveride this function
*/
void Scheduler::HandleCurrentFlowEmpty()
{
	counter_of_empty_flows++;
	Go_to_Next_Flow();
}

/* This function moves the current flow pointer to the next
flow to be schedule next.
*/

void Scheduler::Go_to_Next_Flow()
{
	if (current_flow == tail_flow_queue)
		current_flow = head_flow_queue;
	else
		current_flow = current_flow->next_flow;
}

